﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DiscreteEventModel {

    using Common;

    public partial class DiscreteEvent<TState, TContext> {

        public interface IProcess {
            IRuntime Runtime { get; }
            bool Step();
        }

        public interface IRuntime {
            DiscreteTime Time { get; }
            TContext Context { get; }
            IEnumerable<Trace> EventTrace { get; }
        }

        internal class Runtime : IRuntime, IProcess {

            public DiscreteTime Time { get; private set; }
            public TContext Context { get; private set; }
            public readonly double EventTracingPeriod;
            public readonly Queue<Trace> EventTrace;
            public readonly PriorityQueue<DiscreteEvent<TState, TContext>> EventQueue;
            public readonly Queue<DiscreteEvent<TState, TContext>> PendingEvents;

            IEnumerable<Trace> IRuntime.EventTrace {
                get { return this.EventTrace; }
            }

            IRuntime IProcess.Runtime {
                get { return this; }
            }

            public bool Step() {
                while(this.PendingEvents.Count > 0) {
                    DiscreteEvent<TState, TContext> pendingEvent = this.PendingEvents.Dequeue();
                    if(pendingEvent.SchedulingStatus == DiscreteEventSchedulingStatus.Pending) {
                        pendingEvent.CommitTime = pendingEvent.Commit(this);
                    }
                    if(pendingEvent.CommitTime.IsInfinity) {
                        this.EventQueue.Dequeue(pendingEvent);
                        pendingEvent.SchedulingStatus = DiscreteEventSchedulingStatus.Unscheduled;
                        pendingEvent.Dispose(this.Context);
                    } else if(pendingEvent.CommitTime >= this.Time) {
                        this.EventQueue.Enqueue(pendingEvent);
                        pendingEvent.SchedulingStatus = DiscreteEventSchedulingStatus.Scheduled;
                    } else {
                        throw new Exception();
                    }
                }

                DiscreteEvent<TState, TContext> currentEvent;
                if(!this.EventQueue.Peek(out currentEvent)) {
                    this.Time = DiscreteTime.Base;
                    this.EventTrace.Clear();
                    return false;
                }

                this.Time = currentEvent.CommitTime;
                while(this.EventTrace.Count > 0 && (this.Time - this.EventTrace.Peek().Time) > this.EventTracingPeriod) {
                    this.EventTrace.Dequeue();
                }

                do {
                    foreach(TState target in currentEvent.Targets) {
                        target.Synchronize(this);
                        if(currentEvent == target) {
                            target.Transit(this.Context);
                        } else {
                            target.Transit(this.Context, currentEvent);
                            if(target.SchedulingStatus != DiscreteEventSchedulingStatus.Pending) {
                                this.PendingEvents.Enqueue(target);
                                target.SchedulingStatus = DiscreteEventSchedulingStatus.Pending;
                            }
                        }
                    }
                    if(currentEvent.SchedulingStatus != DiscreteEventSchedulingStatus.Pending) {
                        this.PendingEvents.Enqueue(currentEvent);
                        currentEvent.SchedulingStatus = DiscreteEventSchedulingStatus.Pending;
                    }
                    if(currentEvent.Traceable) {
                        this.EventTrace.Enqueue(new Trace(currentEvent));
                    }
                } while(this.EventQueue.Peek(out currentEvent) && currentEvent.CommitTime == this.Time);
                return true;
            }

            public Runtime(TContext context, double eventTracingPeriod) {
                this.Time = DiscreteTime.Base;
                this.Context = context;
                this.EventTracingPeriod = eventTracingPeriod;
                this.EventTrace = new Queue<Trace>();
                this.EventQueue = PriorityQueue<DiscreteEvent<TState, TContext>>.Create<DiscreteEvent<TState, TContext>>();
                this.PendingEvents = new Queue<DiscreteEvent<TState, TContext>>();
            }
        }
    }
}
